import sys
from rsa.util.common_utilities import clean
sys.path.insert(0, "/opt/orbitz-st/lib/python/site-packages")

import robot.errors
import robot.parsing.model
import robot.parsing.settings
from rsa.loader.variable_loader import VariableLoader
from rsa.util.robot_utilities import open_resource, root_suite

class SuiteLoader(object):
    
    # Both dictionaries are used to optimize the loading of imported resources,
    # basically once a resource is imported, is stored in this dictionaries, and
    # every reference to the same resource will be loaded from this dictionaries.
    resources = {}
    variables = {}
    variable_loader = VariableLoader()
    
    """
    This class is charge of create a robot.parsing.model.TestData of a given path, and create 
    the symbols table associated to the suites loaded.    
    """
    
    def print_suite(self, suite, indent=0):
        """
        Print the name of a suite, then it's tests and nested suites
        """
        prefix = " " * indent
        print "%s> %s" % (prefix, suite.name)
        for test in suite.testcase_table:
            print "%s  - %s" % (prefix, test.name)
            for step in test.steps:
                args = ""
                if(len(step.args) > 0):
                    for arg in step.args:
                        args = args + " | " + arg
                print "%s  * %s" % (prefix, step.keyword + args)
        for child_suite in suite.children:
            self.print_suite(child_suite, indent + 2)
                       
    def load_symbols_table(self, suite):
        """
        Load and return the symbols symbols of a given suite
        """
        if(not suite): raise RuntimeError("error creating symbols symbols: data source is None")
        rsuite = root_suite(suite)
        symbols = SymbolTable(rsuite.name)
        resources = SymbolTable("Resources")
        self.create_symbols_table(rsuite, symbols)
        resources.variables.extend(symbols.variables)
        self.create_resources_table(resources, self.resources)
        return symbols, resources
                              
    def create_symbols_table(self, suite, stable):
        """
        Recursively create the symbols table of the suites structure
        """
        # load
        for keyword in suite.keywords:
            stable.add_keyword(keyword.name)
        for variable in suite.variable_table.variables:
            stable.add_variable(clean(variable.name[2:-1]))
        # load imported resources
        keywords = []
        variables = []
        resources = []
        try:
            self.load_imports(suite, variables, keywords, resources)
        except Exception:
            pass
        stable.add_keyword(keywords)
        stable.add_resource(resources)
        stable.add_variable(variables)
        # load
        if(len(suite.testcase_table.tests) == 0):
            for child_suite in suite.children:
                table = SymbolTable(stable.name + "." + child_suite.name)
                self.create_symbols_table(child_suite, table)
                stable.add_element(table)
        else:
            for test in suite.testcase_table:
                stable.add_element(test.name)

    def create_resources_table(self, root, resources):
        """
        Create a symbols table for all the resources imported in the system
        """
        for key in resources:
            info = resources.get(key)
            table = SymbolTable(key.split(".")[0])
            for keyword in info[0]:
                table.add_keyword(keyword)
            for variable in info[1]:
                table.add_variable(variable)
            root.add_element(table)
                
    def load_imports(self, node, variables, keywords, imported):
        methods = dir(node)
        if(methods.count("imports") == 0): return
        # try to instantiate every resource and import all the 
        # keywords, and variables
        for imp in node.imports:
            # if the import is a resource
            imported.append(imp)
            if(isinstance(imp, robot.parsing.settings.Resource)):
                self.load_resource(imp, variables, keywords, imported)
            # if the import is a variable
            if(isinstance(imp, robot.parsing.settings.Variables)):
                self.load_variables(imp, variables)
    
    def load_variables(self, imp, variables):
        prefix = ""
        if(len(imp.args) > 0):
            source = imp.args[0]
            prefix = imp.args[2]
        else: 
            source = imp.name
        if(source in self.variables):
            variables.extend(self.variables[source])
        else:
            vars = self.variable_loader.get_variables(imp.directory, source, prefix)
            if(vars != None):
                self.variables[source] = vars
                variables.extend(vars)
    
    def load_resource(self, imp, variables, keywords, resources):
        if(imp.name in self.resources):
            keywords.extend(self.resources[imp.name][0])
            variables.extend(self.resources[imp.name][1])
            resources.extend(self.resources[imp.name][2])
        else:
            resource = open_resource(imp.directory, imp.name)
            vars = []
            keys = []
            rsrc = []
            # import all the variables and keywords
            for var in resource.variable_table.variables:
                vars.append(clean(var.name[2:-1]))
            for keyw in resource.keywords:
                keys.append(keyw.name)
            # search in the imported resources of the resource
            self.load_imports(resource, vars, keys, rsrc)
            # add variable and keywords to the dictionary
            keywords.extend(keys)
            variables.extend(vars)
            resources.extend(rsrc)
            self.resources[imp.name] = [keys, vars, rsrc]

    def load_suite(self, suitePath):
        """
        Load the suite structure of the given @suitePath
        """
        try:
            # TestData is a factory that can read both 
            # robot test files and robot test directories.
            suite = robot.parsing.model.TestData(parent=None, source=suitePath)
            # Print suite
            # self.print_suite(suite)
            return suite
        except robot.errors.DataError, e:
            print "error opening '%s':" % suitePath, e
            
    def instance_resource(self, imp):
        return open_resource(imp.directory, imp.name)

class SymbolTable(object):
    """
    The symbols table class represents a data structure that contains a reference to every 
    variable, keyword, resource and test case defined on the system. And provides an easy and
    quick access to them
    """    
    
    def __init__(self, name):
        self.name = name
        self.elements = []
        self.keywords = []
        self.variables = []
        self.resources = []
                
    def add_element(self, element):
        self.add(self.elements, element)
    
    def add_keyword(self, keyword):
        self.add(self.keywords, keyword)
                
    def add_variable(self, variable):
        self.add(self.variables, variable)
        
    def add_resource(self, resource):
        self.add(self.resources, resource)
        
    def add(self, elements, element):
        if(element == None):
            raise Exception("Null Rule reference added to an array; that's a bug!")
        if(isinstance(element, list)):
            elements.extend(element)
        else:
            elements.append(element)
        
    def find_keyword(self, name):
        """
        Return a list of the keywords with the name @name defined in the system.
        e.q ["suite1.keyword","suite2.keyword"]
        """
        keywords = []
        for keyword in self.keywords:
            if(keyword == name):
                keywords.append(self.name + '.' + keyword)
        for element in self.elements:
            if(isinstance(element, SymbolTable)):
                keywords.extend(element.find_keyword(name))
        return keywords
    
    def get_test_case(self, name):
        """
        Return a list of the test cases with the name @name defined in the system.
        e.q ["suite1.testcase","suite2.testcase"]
        """
        testcases = []
        for element in self.elements:
            if(isinstance(element, SymbolTable)):
                testcases.extend(element.get_test_case(name))
            elif(element == name):
                testcases.append(self.name + '.' + element)
        return testcases
    
    def get_variable(self, name, suite):
        """
        Return a list of the variables with the name @name defined in the system.
        e.q ["suite1.testcase1.var","suite1.testcase2.var"]
        """
        if(self.valid_suite(self.name, suite)):
            for variable in self.variables:
                if(variable.lower() == name.lower()):
                    return variable
        for element in self.elements:
            if(isinstance(element, SymbolTable)):
                var = element.get_variable(name, suite)
                if(var != None):
                    return var
        return None
    
    def valid_suite(self, n1, n2):
        if(n1 == n2 or "Resources" in n1):
            return True
        n1 = n1.rpartition('/')[2]
        n1 = n1.replace('_', ' ').replace('-', ' ')
        n2 = n2.replace('_', ' ').replace('-', ' ')
        return n1 == n2
    
    def get_resources(self, name):
        if(self.name == name):
            return self.resources
        else:
            for element in self.elements:
                if(isinstance(element, SymbolTable)):
                    if(name.find(element.name) == 0):
                        resources = element.get_resources(name)
                        if(resources):
                            return resources
    
    def get_suite_keywords(self, suite):
        if(self.name == suite):
            return self.keywords
        for element in self.elements:
            if(isinstance(element, SymbolTable)):
                keywords = element.get_suite_keywords(suite)
                if(keywords != None):
                    return keywords
        return None

class Parser(object):
    """
    The parser class has the ability of analyzing a robot script, transform it into a 
    Python representation and then made of a sequence of tokens based on a AST representation 
    """    
    
    def __init__(self):
        pass
    
    def parse(self, test):
        """
        Parses a robot.parsing.model.TestData into an AST representation. To do this is 
        necessary to transform the robot syntax of the test into Python syntax.
        """
        ncode = ""
        for step in test.steps:
            ncode += self.parse_sentence(step) + '\n'
        #return ast.parse(ncode)
    
    def parse_sentence(self, step):
        """
        Parses a Robot sentence into a Python sentence
        """
        if(isinstance(step, robot.parsing.model.Step)):
            if(step.keyword.find('FOR') != -1):
                return self.parse_loop(step)
            if(len(step.assign) == 0):
                return self.parse_call(step)
            if(len(step.assign) > 0):
                return self.parse_assigment(step)
        elif(isinstance(step, robot.parsing.model.ForLoop)):
            return self.parse_loop(step)
        else:
            return self.parse_variable(step)

    def parse_loop(self, loop):
        """
        Parses a Robot loop (:FOR) into a Python loop
        """
        nsentence = 'for ' + self.parse_variable(loop.vars[0])
        if(loop.range):
            nsentence += ' in range['
            for item in loop.items:
                nsentence += self.parse_variable(item) + ':'
            nsentence = nsentence[:-1] + ']: \n'
            for step in loop.steps:
                nsentence += '\t' + self.parse_sentence(step) + '\n'
        return nsentence

    def parse_assigment(self, step):
        """
        Parses a Robot assignment into Python assignment
        """
        nsentence = self.parse_variable(step.assign[0]) + step.keyword.replace(' ', '') + '('
        if(len(step.args) > 0):
            for arg in step.args:
                nsentence += self.parse_sentence(arg) + ','
            nsentence = nsentence[:-1]
        return nsentence + ')'
    
    def parse_call(self, step):
        """
        Parses a Robot function call into a Python method call
        """
        nsentence = step.keyword.replace(' ', '') + '('
        if(len(step.args) > 1):
            for var in step.args:
                nsentence += self.parse_sentence(var) + ','
            nsentence = nsentence[:-1]
        return nsentence + ')'
                
    def parse_variable(self, sentence):
        """
        Parses a Robot variable into a Python variable
        """
        if(sentence.find('${') == -1):
            if(sentence.isdigit()):
                return sentence
            return '\'' + sentence + '\''
        return sentence.replace('${', '_').replace('}', '')
